package routeFunctions;

import whereRU.frontEnd.DirectionsActivity;
import whereRU.frontEnd.MyApplication;
import whereRU.frontEnd.R;

import java.util.Calendar;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * This class leverages android location classes to provide single location updates and must be implemented in (or with) an activity.
 * If requesting GPS provider location updates then a cancelable loading screen will appear on the activity
 * 
 * @author Nick Luzzi
 * @version 3
 */
public class GPS extends AsyncTask<String, String, String>{	
	private Activity activity;
	private ProgressDialog dialog;

	private double longitude;
	private double latitude;

	private String provider;
	private boolean isGPSEnabled;
	private boolean isNetEnabled;
	private boolean hasFix;
	private Location location;
	private LocationManager manager;

	/**
	 * Constructor for this GPS object that requires the activity this GPS will be running on.
	 * @param act the activity for which the gps services will be provided.
	 */
	public GPS(Activity activity) {
		this.activity = activity;
	}

	/**
	 * Allows provider to be set to determine if an asynchronous background worker will be needed.
	 * @param provider the provide that location updates will be requested from.
	 * 			  	   (either LocationManager.GPS_PROVIDER or LocationManager.NETWORK_PROVIDER)
	 */
	public void setProvider(String provider){
		this.provider = provider;
	}

	/**
	 * This method is available to allow the location listener to start receiving gps updates.
	 */
	public void startGPS(){
		manager = (LocationManager)activity.getSystemService(Context.LOCATION_SERVICE);
		try{
			isGPSEnabled = manager.isProviderEnabled(LocationManager.GPS_PROVIDER);
		}
		catch (Exception e){
			isGPSEnabled = false;
		}
		try{
			isNetEnabled = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
		} 
		catch (Exception e) {
			isNetEnabled = false;
		}
		manager.addGpsStatusListener(gpsStatusListener);
	}	
	
	/**
	 * This method is available to allow the location listener updates to stop
	 * whenever requested.
	 */
	public void stopGPS(){
		hasFix = false;
		manager.removeUpdates(locationListener);
		manager.removeGpsStatusListener(gpsStatusListener);
	}

	/**
	 * This method allows this GPS object to request the new device location.
	 * @param provider The source that the coordinates will be requested from.
	 * @return an integer representing the success of this method:
	 * 			-2 if gps is still tracking location
	 * 			-1 if no location was obtained 
	 * 			 0 if the same previous location has been received 
	 * 			 1 if a location was successfully obtained
	 */
	public int getCoordinates()  {
		if(provider.equals(LocationManager.GPS_PROVIDER)){
			if (isGPSEnabled) {
				if(!hasFix){
					manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
					this.execute("");
					if(!hasFix)
						return -2;					
				}
			}
		}
		if(provider.equals(LocationManager.NETWORK_PROVIDER)){
			if (isNetEnabled) {
				manager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
				location = manager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
			}
		}

		int result = -1;
		if (location != null){
			if(!(latitude == location.getLatitude()) && !(longitude == location.getLongitude())){
				latitude = location.getLatitude();
				longitude = location.getLongitude();
				result = 1;
			}
			else 
				result = 0;
		} 
		return result;
	}

	/**
	 * A public accessor method for the last obtained coordinates as a Loc object.
	 * @return A Loc containing the devices last captured coordinates.
	 */
	public Loc getLastLoc(){
		return new Loc(latitude, longitude);
	}

	/**
	 * A public accessor method for the last obtained latitude coordinate of the device.
	 * @return A double representing the user's longitude coordinate value.
	 */
	public double getLastLongitude(){
		return longitude;
	}

	/**
	 * A public accessor method for the last obtained latitude coordinate of the device.
	 * @return A double representing the user's latitude coordinate value.
	 */
	public double getLastLatitude(){
		return latitude;
	}

	/**
	 * This method obtains the user's angle to North relative to a destination position.
	 * @param lat The destination latitude coordinate value.
	 * @param lon The destination's longitude coordinate value.
	 * @return A double representing the angle East of true North.
	 */
	public double angleToNorth(double lat, double lon){
		Location destination = new Location(isGPSEnabled? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER);
		destination.setLatitude(lat);
		destination.setLongitude(lon);
		return location.bearingTo(destination);
	} 

	public void setLocation(Location loc){
		this.location = loc;
	}

	/**
	 * This method returns the devices bearing, based on GPS services,
	 * in degrees East of true North.
	 * @return A double representing the devices bearing in degrees East of true North.
	 */
	public double getAngleTrue(){
		double result = 0d;
		if(location != null && location.hasBearing())
			result = location.getBearing();
		return result;		   
	}	

	/**
	 * This is the LocationListener for this GPS object.
	 */
	private final LocationListener locationListener = new LocationListener() {
		public void onLocationChanged(Location location) {
			setLocation(location);
		}
		public void onProviderDisabled(String provider) {

		}
		public void onProviderEnabled(String provider) {

		}
		public void onStatusChanged(String provider, int status, Bundle extras) {

		}
	};

	/**
	 * This is a Listener to determine when GPS has a received a fix from the satellites.
	 */
	GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener(){
		public void onGpsStatusChanged(int event) {
			if(event == GpsStatus.GPS_EVENT_FIRST_FIX) {
				location = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
				if(!(latitude == location.getLatitude()) && !(longitude == location.getLongitude())){
					latitude = location.getLatitude();
					longitude = location.getLongitude();
				}
				Loc userLoc = new Loc(latitude, longitude);
				((MyApplication) activity.getApplication()).setUserLocation(userLoc);
				hasFix = true;
			}			
		}
	};

	/**
	 * Sets up and shows a Progress dialog that can be cancelled.
	 * Runs directly before doInBackground. 
	 */
	@Override
	protected void onPreExecute(){
		dialog = new ProgressDialog(activity);
		dialog.setTitle("Obtaining GPS Fix");
		dialog.setMessage("Will run for 1 minute maximum, Please Wait...");
		dialog.setIndeterminate(true);
		dialog.setCancelable(true);
		dialog.setButton("Cancel", listener);
		dialog.show();
	}
	
	/**
	 * This method is called on execute of this asynchronous task.  The task waits for 1 minute for a
	 * satellite fix from the GPSstatus.Listener.
	 */
	@Override
	protected String doInBackground(String... arg0) {
		Long t = Calendar.getInstance().getTimeInMillis();
		while (!hasFix && Calendar.getInstance().getTimeInMillis() - t < 60000) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}	
		return "";
	}
	
	/**
	 * This method runs directly after successful completion of doInBackground
	 * without being canceled through the dialog.
	 */
	@Override 
	protected void onPostExecute(String result){
		Button button = (Button) activity.findViewById(R.id.gpsFixButton);
		button.setVisibility(View.GONE);
		stopGPS();
		dialog.dismiss();
	}
	
	/**
	 * If the loading dialog is cancelled this code runs.
	 */
	@Override
	protected void onCancelled(){
		stopGPS();
		activity.startActivity(new Intent(activity, DirectionsActivity.class));   
	}	

	/**
	 * Allows the OnClickListener to cancel this AysncTask.
	 */
	public void cancelTask(){
		this.cancel(true);
	}
	
	/**
	 * An event handler for her loading dialog's cancel button.
	 * Closes the dialog and cancels stops the gps listeners.
	 */
	private OnClickListener listener = new OnClickListener(){
		public void onClick(DialogInterface dialog, int which) {			
			dialog.cancel();
			cancelTask();			
		}		
	};	
}